﻿////////////////////////////////////////////////////////////////////
//
// (c) 2002 Microsoft Press
// by Rob Jarrett
//
// This class implements various functions that are related to
// rendering ink. 
//
// Taken with permission from the authors. 
//
////////////////////////////////////////////////////////////////////

using System;
using System.Collections;
using System.Drawing;
using Microsoft.Ink;

namespace MokkAnnotator
{
    public class RendererEx
    {
        // Convert a rectangle in ink space to pixels
        public static void InkSpaceToPixel(
            Renderer renderer, Graphics g, ref Rectangle rcRect)
        {
            Point ptTopLeft = rcRect.Location;
            Point ptBtmRght = new Point(rcRect.Right, rcRect.Bottom);

            renderer.InkSpaceToPixel(g, ref ptTopLeft);
            renderer.InkSpaceToPixel(g, ref ptBtmRght);

            rcRect.Location = ptTopLeft;
            rcRect.Size = new Size(ptBtmRght.X - ptTopLeft.X,
                ptBtmRght.Y - ptTopLeft.Y);
        }

        // Convert a rectangle in pixels to ink space
        public static void PixelToInkSpace(
            Renderer renderer, Graphics g, ref Rectangle rcRect)
        {
            Point ptTopLeft = rcRect.Location;
            Point ptBtmRght = new Point(rcRect.Right, rcRect.Bottom);

            renderer.PixelToInkSpace(g, ref ptTopLeft);
            renderer.PixelToInkSpace(g, ref ptBtmRght);

            rcRect.Location = ptTopLeft;
            rcRect.Size = new Size(ptBtmRght.X - ptTopLeft.X,
                ptBtmRght.Y - ptTopLeft.Y);
        }

        // Draw a strokes collection in a selected state
        public static void DrawSelected(
            Renderer renderer, Graphics g, Microsoft.Ink.Strokes strokes)
        {
            foreach (Stroke s in strokes)
            {
                if (!s.Deleted)
                {
                    DrawSelected(renderer, g, s);
                }
            }
        }

        // Draw a stroke in a selected state
        public static void DrawSelected(
            Renderer renderer, Graphics g, Stroke s)
        {
            // Compute selection ink thickness
            Point ptThickness = new Point(4, 4);
            renderer.PixelToInkSpace(g, ref ptThickness);

            // Copy the DrawingAttributes since we値l be modifying them
            DrawingAttributes da = s.DrawingAttributes.Clone();

            // Turn off pressure so thickness draws uniformly
            da.IgnorePressure = true;

            // Draw the thick outer stroke - the "outline"
            da.Width += ptThickness.X;
            da.Height += ptThickness.Y;
            renderer.Draw(g, s, da);

            // Draw the inner "transparent" stroke
            da.Width -= ptThickness.X;
            da.Height -= ptThickness.Y;
            da.Color = Color.White;
            renderer.Draw(g, s, da);
        }

        // Draw the stroke IDs in the top-left corner of each stroke's
        // bounding rectangle for an Ink object
        public static void DrawStrokeIds(Graphics g, Font font, Ink ink)
        {
            DrawStrokeIds(new Renderer(), g, font, ink.Strokes);
        }

        // Draw the stroke IDs for a Strokes collection
        public static void DrawStrokeIds(
            Renderer renderer, Graphics g, Font font, Microsoft.Ink.Strokes strokes)
        {
            // Iterate through every stroke referenced by the collection
            foreach (Stroke s in strokes)
            {
                // Make sure each stroke has not been deleted
                if (!s.Deleted)
                {
                    // Draw the stroke's ID at its starting point
                    string str = s.Id.ToString();
                    Point pt = s.GetPoint(0);
                    renderer.InkSpaceToPixel(g, ref pt);
                    //g.DrawString(
                    //    str, font, Brushes.White, pt.X - 10, pt.Y - 10);
                    //g.DrawString(
                    //    str, font, Brushes.White, pt.X + 1, pt.Y + 1);
                    g.DrawString(
                        str, font, Brushes.Black, pt.X, pt.Y);
                }
            }
        }

        // Draw the bounding rect of each stroke in an Ink object
        public static void DrawBoundingBoxes(Graphics g, Ink ink)
        {
            DrawBoundingBoxes(new Renderer(), g, ink.Strokes,
                Pens.LightGreen, BoundingBoxMode.Default);
        }

        // Draw the various bounding rects of each stroke in a strokes
        // collection
        public static void DrawBoundingBoxes(Renderer renderer,
            Graphics g, Microsoft.Ink.Strokes strokes, Pen pen, BoundingBoxMode mode)
        {
            foreach (Stroke s in strokes)
            {
                // Make sure each stroke has not been deleted
                if (!s.Deleted)
                {
                    Rectangle rcBounds = s.GetBoundingBox(mode);
                    RendererEx.InkSpaceToPixel(renderer, g, ref rcBounds);
                    g.DrawRectangle(pen, rcBounds);
                }
            }
        }

        public enum StrokePointType
        {
            Polyline,
            Bezier,
            FlattenedBezier
        }

        // Draw the polyline points each stroke has in an Ink object
        public static void DrawPoints(Graphics g, Ink ink)
        {
            DrawPoints(new Renderer(), g, ink.Strokes, Brushes.Red,
                StrokePointType.Polyline);
        }

        // Draw the various kinds of points each stroke has in a
        // strokes collection
        public static void DrawPoints(Renderer renderer, Graphics g,
            Microsoft.Ink.Strokes strokes, Brush brush, StrokePointType type)
        {
            foreach (Stroke s in strokes)
            {
                // Make sure each stroke has not been deleted
                if (!s.Deleted)
                {
                    DrawPoints(renderer, g, s, brush, type);
                }
            }
        }

        // Draw the various kinds of points for a stroke
        public static void DrawPoints(Renderer renderer, Graphics g,
            Stroke stroke, Brush brush, StrokePointType type)
        {
            // Get the array of points according to the type desired
            Point[] pts = null;
            switch (type)
            {
                case StrokePointType.Bezier:
                    pts = stroke.BezierPoints;
                    break;

                case StrokePointType.FlattenedBezier:
                    pts = stroke.GetFlattenedBezierPoints();
                    break;

                case StrokePointType.Polyline:
                    pts = stroke.GetPoints();
                    break;
            }

            // Render the points if they were retrieved
            if (pts != null)
            {
                renderer.InkSpaceToPixel(g, ref pts);
                foreach (Point pt in pts)
                {
                    g.FillEllipse(brush, pt.X - 1, pt.Y - 1, 4, 4);
                }
            }
        }

        public enum StrokeIntersectionType
        {
            Self,
            Stroke,
            BoundingBox
        }

        // Draw the stroke intersections for the strokes in an Ink object
        public static void DrawIntersections(Graphics g, Ink ink)
        {
            DrawIntersections(new Renderer(), g, ink.Strokes,
                Pens.DarkRed, StrokeIntersectionType.Stroke);
        }

        // Draw the various intersections for the strokes in a Strokes
        // collection
        public static void DrawIntersections(
            Renderer renderer, Graphics g, Microsoft.Ink.Strokes strokes,
            Pen pen, StrokeIntersectionType type)
        {
            foreach (Stroke s in strokes)
            {
                // Make sure each stroke has not been deleted
                if (!s.Deleted)
                {
                    if (type == StrokeIntersectionType.BoundingBox)
                    {
                        // Draw bounding box intersections
                        DrawIntersections(renderer, g, s, pen);
                    }
                    else
                    {
                        // Compute either self or stroke intersections
                        float[] fResults = null;
                        switch (type)
                        {
                            case StrokeIntersectionType.Self:
                                fResults = s.SelfIntersections;
                                break;

                            case StrokeIntersectionType.Stroke:
                                fResults = s.FindIntersections(null);
                                break;
                        }

                        // Draw the intersections (if any)
                        if (fResults != null)
                        {
                            DrawIntersection(
                                renderer, g, s, fResults, pen);
                        }
                    }
                }
            }
        }

        // Helper function to draw intersections between each stroke's
        // bounding rect and all other strokes
        private static void DrawIntersections(Renderer renderer,
            Graphics g, Stroke s, Pen pen)
        {
            Rectangle rcBounds = renderer.Measure(s);

            // Generate a strokes collection containing all strokes
            // except the one being used for its bounding rect
            Microsoft.Ink.Strokes strksToCheck = s.Ink.CreateStrokes();
            strksToCheck.Add(s.Ink.Strokes);
            strksToCheck.Remove(s);

            foreach (Stroke s2 in strksToCheck)
            {
                if (!s2.Deleted)
                {
                    StrokeIntersection[] siResults =
                        s2.GetRectangleIntersections(rcBounds);

                    ArrayList arrResults = new ArrayList();
                    foreach (StrokeIntersection si in siResults)
                    {
                        if (si.BeginIndex >= 0)
                        {
                            arrResults.Add(si.BeginIndex);
                        }
                        if (si.EndIndex > 0)
                        {
                            arrResults.Add(si.EndIndex);
                        }
                    }

                    if (arrResults.Count > 0)
                    {
                        // Fill up a float array with the intersection
                        // indicies
                        float[] fResults =
                            (float[])arrResults.ToArray(typeof(float));

                        // Use a handy helper to draw the intersections
                        DrawIntersection(renderer, g, s2, fResults, pen);
                    }
                }
            }
        }

        // Helper function to draw intersections along a stroke
        private static void DrawIntersection(Renderer renderer,
            Graphics g, Stroke s, float[] fIndicies, Pen pen)
        {
            foreach (float fIndex in fIndicies)
            {
                // Get the whole-number point of the intersection
                int nIndex = (int)fIndex;
                Point pt = s.GetPoint(nIndex);

                // Compute the fractional point of the intersection if
                // possible
                if (nIndex < s.GetPoints().Length - 1)
                {
                    float fFraction = fIndex - nIndex;
                    Point pt2 = s.GetPoint(nIndex + 1);
                    pt.X += (int)Math.Round((pt2.X - pt.X) * fFraction);
                    pt.Y += (int)Math.Round((pt2.Y - pt.Y) * fFraction);
                }

                renderer.InkSpaceToPixel(g, ref pt);
                g.DrawEllipse(pen, pt.X - 3, pt.Y - 3, 7, 7);
            }
        }

        public enum StrokeCuspType
        {
            Bezier,
            Polyline
        }

        // Draw the polyline cusps for the strokes in an Ink object
        public static void DrawCusps(Graphics g, Ink ink)
        {
            DrawCusps(new Renderer(), g, ink.Strokes,
                Pens.Blue, StrokeCuspType.Polyline);
        }

        // Draw the various cusps for the strokes in a strokes
        // collection
        public static void DrawCusps(Renderer renderer, Graphics g,
            Microsoft.Ink.Strokes strokes, Pen pen, StrokeCuspType type)
        {
            foreach (Stroke s in strokes)
            {
                // Make sure each stroke has not been deleted
                if (!s.Deleted)
                {
                    DrawCusps(renderer, g, s, pen, type);
                }
            }
        }

        // Draw the various cusps for a stroke
        public static void DrawCusps(Renderer renderer, Graphics g,
            Stroke stroke, Pen pen, StrokeCuspType type)
        {
            // Get the cusp indexes and point array according to the
            // type desired
            int[] cusps = null;
            Point[] pts = null;
            switch (type)
            {
                case StrokeCuspType.Bezier:
                    cusps = stroke.BezierCusps;
                    pts = stroke.BezierPoints;
                    break;

                case StrokeCuspType.Polyline:
                    cusps = stroke.PolylineCusps;
                    pts = stroke.GetPoints();
                    break;
            }

            // Render the points if they were retrieved
            if (cusps != null && pts != null)
            {
                foreach (int n in cusps)
                {
                    Point pt = pts[n];
                    renderer.InkSpaceToPixel(g, ref pt);
                    g.DrawEllipse(pen, pt.X - 3, pt.Y - 3, 7, 7);
                }
            }
        }
    }
}
